package cn.com.pc.content.service;

import cn.com.pc.content.constant.TagConstants;
import cn.com.pc.content.domain.Biz;
import cn.com.pc.content.domain.Site;
import cn.com.pc.content.domain.tag.Tag;
import cn.com.pc.content.domain.tag.TagRelation;
import cn.com.pc.content.domain.tag.TagRepository;
import cn.com.pc.content.dto.*;
import cn.com.pc.content.event.TagEvent;
import cn.com.pc.content.service.es.ESQueryService;
import cn.com.pc.content.util.ElasticUtil;
import cn.com.pc.content.util.SpringCtxUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.*;
import javax.transaction.Transactional;
import java.util.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class TagService {

    private final TagRepository tagRepository;

    private final ESQueryService esQueryService;

    /**
     * 新增标签
     * @param tagDTO
     * @return
     */
    @SneakyThrows
    @Transactional
    public Long createTag(TagDTO tagDTO) {
        // 保存至mysql
        Tag tag = new Tag();
        BeanUtils.copyProperties(tagDTO, tag);
        tag = tagRepository.save(tag);

        // 保存至es
        String treeName = tagDTO.getTreeName();
        tagDTO = TagDTO.from(tag);
        tagDTO.setTreeName(treeName);

        SpringCtxUtils.publishEvent(new TagEvent(tagDTO, "createTag", Map.of("indexName", TagConstants.TAG_IDX, "id", String.valueOf(tag.getId()))));
        return tag.getId();
    }


    /**
     * 修改标签
     * @param tagDTO
     * @param tag
     * @return
     */
    @SneakyThrows
    @Transactional
    public Tag updateTag(TagDTO tagDTO, Tag tag) {
        // 保存至mysql
        BeanUtils.copyProperties(tagDTO, tag);
        tag = tagRepository.save(tag);

        // 保存至es
        String treeName = tagDTO.getTreeName();
        tagDTO = TagDTO.from(tag);
        tagDTO.setTreeName(treeName);

        SpringCtxUtils.publishEvent(new TagEvent(tagDTO, "updateTag", Map.of("indexName", TagConstants.TAG_IDX, "id", String.valueOf(tag.getId()))));
        return tag;
    }


    /**
     * 删除标签
     * @param tag
     */
    @SneakyThrows
    @Transactional
    public void deleteTag(Tag tag) {
        // 保存至mysql
        tagRepository.delete(tag);

        // 保存至es
        SpringCtxUtils.publishEvent(new TagEvent("", "deleteTag", Map.of("indexName", TagConstants.TAG_IDX, "id", String.valueOf(tag.getId()))));

    }


    /**
     * ES查询标签列表
     * @param tagForm
     * @param pageForm
     * @return
     */
    public PageResult<TagDTO> listTagsByForm(TagForm tagForm, PageForm pageForm, Sort sort) {
        // 查询
        BoolQueryBuilder boolQueryBuilder = tagQueryFilter(tagForm);

        // 排序
        SortBuilder<?> sortBuilder;
        String orderBy = sort.getSortBy();
        if (StringUtils.hasLength(orderBy)) {
            sortBuilder = SortBuilders.fieldSort(orderBy).order(sort.getSortOrder());
        } else {
            sortBuilder = SortBuilders.scoreSort().order(SortOrder.DESC);
        }

        // 分页
        SearchSourceBuilder searchSourceBuilder = ElasticUtil.initSearchSourceBuilder(boolQueryBuilder, sortBuilder, (pageForm.getPageNum() - 1) * pageForm.getPageSize(), pageForm.getPageSize(), 60, true);

        // 执行搜索
        return esQueryService.search(searchSourceBuilder, TagDTO.class, pageForm.getPageNum(), pageForm.getPageSize(), TagConstants.TAG_IDX);
    }


    /**
     * 标签搜索过滤
     * @param tagForm
     * @return
     */
    public BoolQueryBuilder tagQueryFilter(TagForm tagForm) {

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("site", tagForm.getSite()));
        if (!CollectionUtils.isEmpty(tagForm.getIds())) {
            boolQueryBuilder = boolQueryBuilder.must(QueryBuilders.termsQuery("id", tagForm.getIds()));
        }
        if (tagForm.getBiz() != null) {
            boolQueryBuilder = boolQueryBuilder.must(QueryBuilders.termQuery("biz", tagForm.getBiz()));
        }
        if (StringUtils.hasLength(tagForm.getName())) {
            boolQueryBuilder = boolQueryBuilder.must(QueryBuilders.boolQuery().should(QueryBuilders.matchPhraseQuery("name.iks", tagForm.getName())).should(QueryBuilders.matchPhraseQuery("name.fpy", tagForm.getName()).slop(1)).should(QueryBuilders.matchPhraseQuery("name.spy", tagForm.getName())));
        }
        if (tagForm.getTreeId() != null) {
            boolQueryBuilder = boolQueryBuilder.must(QueryBuilders.termQuery("treeId", tagForm.getTreeId()));
        }
        if (tagForm.getOnShelve() != null) {
            boolQueryBuilder = boolQueryBuilder.must(QueryBuilders.termQuery("onShelve", tagForm.getOnShelve()));
        }
        if (StringUtils.hasLength(tagForm.getTreeName())) {
            boolQueryBuilder = boolQueryBuilder.must(QueryBuilders.boolQuery().should(QueryBuilders.matchPhraseQuery("treeName.iks", tagForm.getTreeName())).should(QueryBuilders.matchPhraseQuery("treeName.fpy", tagForm.getTreeName()).slop(1)).should(QueryBuilders.matchPhraseQuery("treeName.spy", tagForm.getTreeName())));
        }
        return boolQueryBuilder;
    }


    /**
     * 从列表中过滤有效标签
     * @param tagIds
     * @param site
     * @return
     */
    public Map<String, Object> verifyTagIds(Set<Long> tagIds, Site site) {
        return verifyTagIds(tagIds, site, false);
    }

    public Map<String, Object> verifyTagIds(Set<Long> tagIds, Site site, boolean ignoreOnShelve) {
        // 校验标签id列表
        List<String> tagNames = new ArrayList<>();
        List<Tag> tags = new ArrayList<>();
        Iterator<Long> iterator = tagIds.iterator();
        StringBuilder sb = new StringBuilder();
        while (iterator.hasNext()) {
            Long next = iterator.next();
            Tag tag = this.getTagById(next);
            if (tag == null) {
                sb.append("tagId=").append(next).append("标签不存在。");
                iterator.remove();
            } else if (!tag.getOnShelve() && !ignoreOnShelve) {
                sb.append("tagId=").append(next).append("标签已下架。");
                iterator.remove();
            } else if (tag.getSite() != site) {
                sb.append("tagId=").append(next).append("标签站点有误。");
                iterator.remove();
            } else {
                tagNames.add(tag.getName());
                tags.add(tag);
            }
        }
        if (StringUtils.hasLength(sb.toString())) {
            log.warn("标签校验失败: " + sb.toString());
        }
        return new HashMap<String, Object>() {{
            put("tagIds", tagIds);
            put("tagNames", tagNames);
            put("tags", tags);
        }};
    }


    /**
     * Mysql查询标签列表
     * @param tagTreeId
     * @param tagRelationDTO
     * @return
     */
    public PageResult<Tag> listTagsByTreeIdAndTagRelationDTO(Long tagTreeId, TagRelationDTO tagRelationDTO, PageForm pageForm) {
        Pageable pageable = PageRequest.of(pageForm.getPageNum() - 1, pageForm.getPageSize());
        Page<Tag> tagPage = tagRepository.findAll(findTagsByTreeIdAndTagRelationDTO(tagTreeId, tagRelationDTO), pageable);
        return PageResult.fromPage(tagPage);
    }

    private Specification<Tag> findTagsByTreeIdAndTagRelationDTO(Long tagTreeId, TagRelationDTO tagRelationDTO) {
        return new Specification<Tag>() {
            @Override
            public Predicate toPredicate(@NonNull Root<Tag> root, @NonNull CriteriaQuery<?> query, @NonNull CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>();
                if (tagTreeId != null) {
                    predicates.add(cb.equal(root.get("treeId"), tagTreeId));
                }
                if (tagRelationDTO.getOnShelve() != null) {
                    predicates.add(cb.equal(root.get("onShelve"), tagRelationDTO.getOnShelve()));
                }
                if (tagRelationDTO.getLeftId() != null) {
                    if (tagRelationDTO.getLeftId() == 0) {
                        Join<TagRelation, Tag> left = root.join("rightRelations", JoinType.LEFT);
                        predicates.add(cb.isNull(left.get("relationKey").get("left")));
                    } else {
                        Join<TagRelation, Tag> left = root.join("rightRelations", JoinType.INNER);
                        predicates.add(cb.equal(left.get("relationKey").get("left"), tagRelationDTO.getLeftId()));
                    }
                }
                if (tagRelationDTO.getRightId() != null) {
                    if (tagRelationDTO.getRightId() == 0) {
                        Join<TagRelation, Tag> riht = root.join("leftRelations", JoinType.LEFT);
                        predicates.add(cb.isNull(riht.get("relationKey").get("right")));
                    } else {
                        Join<TagRelation, Tag> riht = root.join("leftRelations", JoinType.INNER);
                        predicates.add(cb.equal(riht.get("relationKey").get("right"), tagRelationDTO.getRightId()));
                    }
                }
                if (StringUtils.hasLength(tagRelationDTO.getTagName())) {
                    predicates.add(cb.like(root.get("name"), "%" + tagRelationDTO.getTagName() + "%"));
                }
                return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
            }
        };
    };


    /**
     * ES查询标签详情
     * @param id
     * @return
     */
    public TagDTO searchTagById(Long id) {
        // es查询标签
        TermQueryBuilder queryBuilder = QueryBuilders.termQuery("id", id);
        SearchSourceBuilder searchSourceBuilder = ElasticUtil.initSearchSourceBuilder(queryBuilder);
        List<TagDTO> search = esQueryService.search(searchSourceBuilder, TagDTO.class, TagConstants.TAG_IDX);
        return CollectionUtils.isEmpty(search) ? null : search.get(0);
    }


    public Tag getTagById(Long id) {
        return tagRepository.findById(id).orElse(null);
    }

    public boolean existsTagByTreeId(Long treeId) {
        return tagRepository.existsByTreeId(treeId);
    }

    public Tag getTagByName(Site site, Biz biz, String name) {
        return tagRepository.findBySiteAndBizAndName(site, biz, name);
    }
}
